package org.bookshare.document.daisy;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.benetech.collections.IdTree;
import org.benetech.collections.ListMap;
import org.benetech.event.EventListener;
import org.benetech.util.FileUtils;
import org.bookshare.document.navigation.NavigationElement;
import org.bookshare.document.navigation.NavigationElement.NavigationElementType;
import org.jdom.Attribute;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;

/**
 * An NCX navigation file.
 * @author Reuben Firmin
 */
public final class NCXNavigationFile {

	private Document ncxDocument;
	private File ncxFile;

    /**
     * Given relevant data, generate an NCX file for a DAISY book.
     * @param smilFile the SMIL file
	 * @param xmlBook the book
	 * @param opf The OPF
	 * @param tmpPath A temporary path to create the file in
     * @param eventListener Class to provide feedback to the user
     * @throws IOException if file access fails
     */
    public NCXNavigationFile(final File tmpPath, final SMILNavigationFile smilFile, final DTBook xmlBook,
    		final OPFPackageFile opf, final EventListener eventListener)
    throws IOException
    {
    	this.ncxDocument = new Document();
    	ncxDocument.setDocType(new DocType("ncx", "-//NISO//DTD ncx v1.1.0//EN",
    			"http://www.loc.gov/nls/z3986/v100/ncx110.dtd"));
    	final Element root = new Element("ncx");
    	root.getAttributes().add(new Attribute("version", "1.1.0"));
        ncxDocument.setRootElement(root);
        final Element head = new Element("head");
        root.addContent(head);
        addMetadata(head, xmlBook.getLevelDepth(), xmlBook.getMaxNormalPage(), xmlBook.getNumberFrontPages(),
        		xmlBook.getNumberNormalPages(), xmlBook.getNumberSpecialPages(), opf.getUID());

        final Element title = new Element("title");
        final ListMap<String, Element> metadataMap = opf.getMetadataMap();
        title.addContent(metadataMap.getFirst("title").getTextTrim());
        head.addContent(title);
        final Element docTitle = new Element("docTitle");
        final Element titletext = new Element("text");
        titletext.addContent(metadataMap.getFirst("title").getTextTrim());
        docTitle.addContent(titletext);
        root.addContent(docTitle);
        root.addContent(getNavMap(xmlBook, smilFile, eventListener));
        root.addContent(getNavList(xmlBook, smilFile, eventListener));
        final String ncxName = opf.getBaseName() + ".ncx";
        this.ncxFile = new File(tmpPath.getAbsolutePath() + File.separator + ncxName);
        eventListener.message("Creating NCX file: " + ncxFile.getAbsolutePath());
        FileUtils.saveDocumentToFile(ncxDocument, ncxFile, true);
    }

    /**
     * Add metadata to the head.
     * @param head The head element
     * @param levelDepth See NCX spec
     * @param maxNormalPage See NCX spec
     * @param numberFrontPages See NCX spec
     * @param numberNormalPages See NCX spec
     * @param numberSpecialPages See NCX spec
     * @param bookUID See NCX spec
     */
    private void addMetadata(final Element head, final int levelDepth, final int maxNormalPage,
    		final int numberFrontPages, final int numberNormalPages, final int numberSpecialPages,
    		final String bookUID)
    {
    	final Element uid = new Element("meta");
    	uid.setAttribute("name", "dtb:uid");
    	uid.setAttribute("content", bookUID);
    	head.addContent(uid);

    	final Element depth = new Element("meta");
    	depth.setAttribute("name", "dtb:depth");
    	depth.setAttribute("content", levelDepth + "");
    	head.addContent(depth);

    	final Element generator = new Element("meta");
    	generator.setAttribute("name", "dtb:generator");
    	generator.setAttribute("content", "Bookshare.org");
    	head.addContent(generator);

    	final Element pageNormal = new Element("meta");
    	pageNormal.setAttribute("name", "dtb:pageNormal");
    	pageNormal.setAttribute("content", numberNormalPages + "");
    	head.addContent(pageNormal);

    	final Element pageSpecial = new Element("meta");
    	pageSpecial.setAttribute("name", "dtb:pageSpecial");
    	pageSpecial.setAttribute("content", numberSpecialPages + "");
    	head.addContent(pageSpecial);

    	final Element pageFront = new Element("meta");
    	pageFront.setAttribute("name", "dtb:pageFront");
    	pageFront.setAttribute("content", numberFrontPages + "");
    	head.addContent(pageFront);

    	final Element maxPageNormal = new Element("meta");
    	maxPageNormal.setAttribute("name", "dtb:maxPageNormal");
    	maxPageNormal.setAttribute("content", maxNormalPage + "");
    	head.addContent(maxPageNormal);
    }

    /**
     * Get the NCX document.
     * @return Never null
     */
    public Document getNCXDocument() {
    	return ncxDocument;
    }

    /**
     * Get the file backing the NCX document.
     * @return Never null
     */
    public File getNCXFile() {
    	return ncxFile;
    }

    /**
     * Add navigation points to the given navMap element.
     * @param xmlBook The xml book
     * @param smilFile The smil file
     * @param listener User feedback class
     * @return never null
     */
    private Element getNavMap(final DTBook xmlBook, final SMILNavigationFile smilFile, final EventListener listener)
    {
    	final Element navMap = new Element("navMap");
    	String lastNavText = null;
    	for (NavigationElement child : xmlBook.getPrimaryNavigation()) {
    		// sometimes there will be navigational elements in the navigation list that yield the same
    		// "nearest" text as others in the same set; so, we ignore those
    		if (child.getText() == null || !child.getText().equals(lastNavText)) {
    	        if (smilFile.getElementById(child.getId()) == null) {
    	        	throw new RuntimeException("Cannot map element not in SMIL (1); id:" + child.getId());
    	        }
    	    	final Element navPoint = new Element("navPoint");
    	        navPoint.getAttributes().add(new Attribute("class", "section"));
    	        navPoint.getAttributes().add(new Attribute(IdTree.ID, child.getId()));
    	        final Element navLabel = new Element("navLabel");
    	        navPoint.addContent(navLabel);
    	        final Element textElement = new Element("text");
    	        navLabel.addContent(textElement);
    	        textElement.addContent(child.getText() != null ? child.getText() : "section_" + child.getId());
    	        final Element content = new Element("content");
    	        navPoint.addContent(content);
    	        content.getAttributes().add(new Attribute("src",
    	        		smilFile.getSMILFile().getName() + "#" + child.getId()));
    			navMap.addContent(navPoint);
    			lastNavText = child.getText();
    		}
    	}
    	return navMap;
    }


    /**
     * Get one to many navlists, derived from the secondary navigation of the book.
     * @param xmlBook The book
     * @param smilFile The smil file
     * @param listener User feedback class
     * @return never null
     */
    private List<Element> getNavList(final DTBook xmlBook, final SMILNavigationFile smilFile,
    		final EventListener listener)
	{
    	final List<Element> navLists = new LinkedList<Element>();
    	for (Map.Entry<NavigationElementType, List<NavigationElement>> nav :
    		xmlBook.getSecondaryNavigation().entrySet())
    	{
        	final Element navList = new Element("navList");
            navList.getAttributes().add(new Attribute(IdTree.ID, nav.getKey().toString()));
            if (nav.getKey().equals(NavigationElementType.PAGE)) {
            	navList.getAttributes().add(new Attribute("class", "pagenum"));
            }
            for (NavigationElement child : nav.getValue()) {
	            if (smilFile.getElementById(child.getId()) == null) {
	            	throw new RuntimeException("Cannot map element not in SMIL (2); id: " + child.getId());
	            }
	        	final String id = child.getId();
	        	final Element navTarget = new Element("navTarget");
	            navList.addContent(navTarget);
	            navTarget.getAttributes().add(new Attribute(IdTree.ID, id));
	            final Element navLabel = new Element("navLabel");
	            navTarget.addContent(navLabel);
	            final Element text = new Element("text");
	            navLabel.addContent(text);
	            text.addContent(child.getText() != null ? child.getText() : "subsection_" + id);
	            final Element content = new Element("content");
	            navTarget.addContent(content);
	            content.getAttributes().add(new Attribute("src", smilFile.getSMILFile().getName() + "#" + id));
	            // mapRef was removed from the 2005 version of the NCX, so we no longer support it. it does not
	            // seem to be important anyway.
            }
            navLists.add(navList);
    	}
    	return navLists;
    }
}
